using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using FernUniHagen.CompilerExtension.DataStructures;
using FernUniHagen.CompilerExtension.Functions;
using FernUniHagen.CompilerExtension.Interfaces;
using Roslyn.Compilers;
using Roslyn.Compilers.Common;
using Roslyn.Compilers.CSharp;

namespace FernUniHagen.CompilerExtension.Adaptor
{
    public class ReturnStatementAdapter : BlockNodeFactory, IMultiplicityConvertible<CommonSyntaxNode>
    {
        ReturnStatementSyntax _returnStatement;

        public ReturnStatementAdapter(ReturnStatementSyntax node)
        {
            _returnStatement = node;
        }

        /// <summary>
        /// TODO: Should call R-MCast
        /// </summary>
        /// <returns></returns>
        public override StatementReplacement<CommonSyntaxNode> ConvertToMutiplicity(string newVariableName)
        {
            var statementsToAdd = new List<StatementSyntax>();
            var identifier = (IdentifierNameSyntax)_returnStatement.DescendantNodesAndSelf().First(n => n is IdentifierNameSyntax);
            var newIdentifier = Syntax.IdentifierName(newVariableName);
            
            StatementReplacement<CommonSyntaxNode> replacement = new StatementReplacement<CommonSyntaxNode>(identifier, newIdentifier);
            var methodDeclaration = _returnStatement.FirstAncestorOrSelf<MethodDeclarationSyntax>();
            var decl = SyntaxElementFactory.GetDeclaration(methodDeclaration, SemanticModel);

            var expression = _returnStatement.Expression;
            if (expression != null)
            {
                //TODO: Throws exception when for example null is returned
                
                var returnValueDeclaration = SemanticModel.FindDecleration(identifier);
                var returnValueDecl = SyntaxElementFactory.GetDeclaration(returnValueDeclaration, SemanticModel);
                AttributeType returnValueAttributeType = returnValueDecl == null ? AttributeType.None : returnValueDecl.MultiplicityType;
                
                //Implementation of R-Return2 (return option or bare as if casted to any)
                //TODO: Refactor to a method as same logic will be used later
                if (decl.MultiplicityType == AttributeType.Any && returnValueAttributeType != AttributeType.Any)
                {
                    var expressionVariable = VariableCreatorService.GetTempVariable();
                    var newVariable = VariableCreatorService.GetTempVariable();
                    var saveExpressionStatement = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, expressionVariable, expression));
                    var initListStmt = Syntax.ParseStatement(string.Format("var {0} = new {1}();" + Environment.NewLine, newVariable, decl.GenericName.ToString()));

                    var condStmt = Syntax.ParseStatement(string.Format("if({0} != null) {1}.Add({0});" + Environment.NewLine, expressionVariable, newVariable));
                    newIdentifier = Syntax.IdentifierName(newVariable);
                    replacement = new StatementReplacement<CommonSyntaxNode>(
                        new List<StatementSyntax>() { saveExpressionStatement, initListStmt, condStmt },
                        _returnStatement.Expression, newIdentifier);

                }
                //Part of the rule R-Return1
                else if (decl.MultiplicityType == AttributeType.Any && returnValueAttributeType == AttributeType.Any)
                {
                    StatementSyntax returnStmt = null;
     

                    //Apply R-Return1 if generic types are identical
                    if (returnValueDecl.GenericName.IsEquivalentTo(decl.GenericName))
                    {
                        return replacement;
                    }
                    //Apply R-Return2 if generic types are not identical
                    else
                    {
                        var expressionVariable = VariableCreatorService.GetTempVariable();
                        var newVariable = VariableCreatorService.GetTempVariable();
                        var saveExpressionStatement = Syntax.ParseStatement(string.Format("var {0} = {1};" + Environment.NewLine, expressionVariable, expression));
                        var initListStmt = Syntax.ParseStatement(string.Format("var {0} = new {1}();" + Environment.NewLine, newVariable, decl.GenericName.ToString()));

                        var condStmt = Syntax.ParseStatement(string.Format("if({0} != null) {1}.AddRange({0});" + Environment.NewLine, expressionVariable, newVariable));
                        returnStmt = Syntax.ParseStatement(string.Format("return {0};" + Environment.NewLine, newVariable));
                        newIdentifier = Syntax.IdentifierName(newVariable);
                        replacement = new StatementReplacement<CommonSyntaxNode>(
                            new List<StatementSyntax>() { saveExpressionStatement, initListStmt, condStmt },
                            _returnStatement.Expression, newIdentifier);
                       
                    }

        
                }

            }
            return replacement;
        }

    }
}
